#include <stdlib.h>
#include <string.h>

#include "jeu.h"
#include "carte_zone.h"
#include "parsing.h"
#include "saisie_clavier_sdl.h"
#include "etat_bataille.h"
#include "etat_partie.h"
#include "manipulation_ennemis.h"
#include "affichage.h"
#include "deplacement.h"
#include "chronometre.h"
#include "actions.h"
#include "inventaire.h"
#include "saisie_clavier_ncurses.h"
#include "hasard.h"
#include "menus.h"
#include "etat_exploration.h"
#include "tab_regions_accessibles.h"

#include "constantes_et_macros.h"



/**
 * \fn static unsigned int calculer_gains_experience(const unsigned int id,
                                                     const struct base_donnees* donnees)
 * \brief Calcule le nombre de points d'experience
 *        obtenus pour avoir vaincu un ennemi donne
 *
 * \param[in] id L'id de l'ennemi concerne
 * \param[in] donnees L'adresse d'une base de donnees correctement remplie
 * \return Le nombre de points d'experience (entier positif) que l'on
 *         obtient en vaincant l'ennemi dont l'id a ete passe en parametre.
 */
static unsigned int calculer_gains_experience(const unsigned int id,
                                              const struct base_donnees* donnees)
{
    return (get_pv_max_par_id(donnees, id) / PTS_VIE_PAR_POINT
          + get_vit_charge_par_id(donnees, id)
          + get_puissance_par_id(donnees, id)
          + get_vit_depl_par_id(donnees, id)) * TAUX_EXP;
}



/**
 * \fn static void calculer_gains_bataille(const struct carte_zone* carte,
                                           struct etat_partie* partie,
                                           const struct base_donnees* donnees)
 * \brief Ajoute de l'experience, des objets et de l'argent a une partie
 *        en fonction de la carte concernee et des ennemis qui s'y trouvaient
 *
 * \param[in] carte L'adresse d'une carte_zone contenant les informations necessaires
 * \param[in,out] partie L'adresse de l'etat de la partie
 * \param[in] donnees L'adresse d'une base de donnees correctement remplie
 *
 * Cette fonction affiche egalement (avec ncurses) des
 * informations sur les gains.
 */
static void calculer_gains_bataille(const struct carte_zone* carte,
                                    struct etat_partie* partie,
                                    const struct base_donnees* donnees)
{
    int i, j;
    struct tab_dyn_id* drops;
    struct tab_dyn_id* probas;
    unsigned int id_ennemi, id_objet, experience, total_experience;
    unsigned char niveau_avant, niveau_apres;
    char buffer[128];
    unsigned int pos_y;
    fenetre_texte* fenetre;

    total_experience = 0;
    demarrer_texte();
    fenetre = ouvrir_fenetre_texte(75, 45, 1, 1);
    afficher_titre_fenetre(fenetre, "Bataille terminee");
    pos_y = 2;
    afficher_dans_boite(fenetre, &pos_y, "Ennemis vaincus :");
    pos_y++;

    /*
     * on parcours la liste des id des ennemis et on affiche des infos
     * pour chacun d'entre eux
     */
    for(i = 0;  i < carte->liste_id_ennemis.nb_elements;  i++)
    {
        id_ennemi = carte->liste_id_ennemis.tab[i];
        total_experience += (experience = calculer_gains_experience(id_ennemi, donnees));
        sprintf(buffer, "=== %s ===", get_nom_ennemi_par_id(donnees, id_ennemi));
        afficher_dans_boite(fenetre, &pos_y, buffer);
        pos_y++;
        sprintf(buffer, "Experience obtenue : %u", experience);
        afficher_dans_boite(fenetre, &pos_y, buffer);
        /* il faut voir si l'ennemi a laisse des objets ou non */
        get_drops_par_id(donnees, &drops, &probas, id_ennemi);
        for(j = 0;  j < drops->nb_elements;  j++)
        {
            /*
             * rappel : les taux de drop sont donnes par des entiers :
             * 0 pour 0%, 500 pour 50%, 1000 pour 100%...
             */
            if(test_proba(TAUX_DROP * probas->tab[j]))
            {
                if(ajouter_exemplaires(&partie->objets_possedes, drops->tab[j], 1))
                    sprintf(buffer, "Obtenu : %s (Nouveau !)",
                            get_nom_objet(donnees, drops->tab[j]));
                else
                    sprintf(buffer, "Obtenu : %s",
                            get_nom_objet(donnees, drops->tab[j]));
                afficher_dans_boite(fenetre, &pos_y, buffer);
            }
        }
        pos_y++;
    }

    sprintf(buffer, "Total experience : %u", total_experience);
    afficher_dans_boite(fenetre, &pos_y, buffer);
    niveau_avant = calculer_niveau(partie->experience, donnees);
    if((partie->experience += total_experience) > MAX_EXPERIENCE)
        partie->experience = MAX_EXPERIENCE;
    niveau_apres = calculer_niveau(partie->experience, donnees);
    if(niveau_apres != niveau_avant)
    {
        sprintf(buffer, "Lvl up ! (%u -> %u)", niveau_avant, niveau_apres);
        afficher_dans_boite(fenetre, &pos_y, buffer);
    }
    sprintf(buffer, "Niveau suivant dans %u points d'experience",
            donner_palier_suivant(niveau_apres, donnees) - partie->experience);
    afficher_dans_boite(fenetre, &pos_y, buffer);

    if(!carte->deja_visitee)
    {
        /* ajout a l'inventaire des objets trouves dans la zone */
        for(i = 0;  i < carte->tab_objets_zone.nb_elements;  i++)
        {
            id_objet = carte->tab_objets_zone.tab[i];
            sprintf(buffer, "Objet \"%s\" trouve dans la zone",
                    get_nom_objet(donnees, id_objet));
            afficher_dans_boite(fenetre, &pos_y, buffer);
            ajouter_exemplaires(&partie->objets_possedes, id_objet, 1);
        }

        if(carte->argent_zone > 0)
        {
            sprintf(buffer, "Vous trouvez de l'argent : %u", carte->argent_zone);
            afficher_dans_boite(fenetre, &pos_y, buffer);
            partie->argent += carte->argent_zone;
        }

        marquer_zone_terminee(&partie->terminees, partie->region, partie->zone);
        if(carte->boss)
        {
            marquer_region_terminee(&partie->reussies, partie->region);
            sprintf(buffer, "Felicitations, vous avez termine cette region !");
            afficher_dans_boite(fenetre, &pos_y, buffer);
        }
    }

    attendre_entree(fenetre);

    if(niveau_avant < niveau_apres)
        menu_ameliorer_competences(partie,
                        NB_POINTS_PAR_NIVEAU * (niveau_apres - niveau_avant));

    fermer_fenetre_texte(fenetre);
    arreter_texte();
}



/**
 * \fn static void convertir_pix_en_zone(const unsigned int x_souris,
                                         const unsigned int y_souris,
                                         char* x_zone, char* y_zone)
 * \brief Convertit les coordonnees du curseur sur la carte de la
 *        region afin de savoir sur quelle zone il se situe.\n
 *        Les coordonnees sont mises a -1 si le
 *        curseur n'est pas sur une region
 *
 * \param[in] x_souris Coordonnee horizontale, en pixels, du curseur
 * \param[in] y_souris Coordonnee verticale, en pixels, du curseur
 * \param[out] x_zone Adresse de l'endroit ou sera stockee la coordonnee
 *                    horizontale de la zone (de 0 a 8 inclus)
 * \param[out] y_zone Adresse de l'endroit ou sera stockee la coordonnee
 *                    verticale de la zone (de 0 a 8 inclus)
 */
static void convertir_pix_en_zone(const unsigned int x_souris,
                                  const unsigned int y_souris,
                                  char* x_zone, char* y_zone)
{
    int temp_x, temp_y;
    int i, j;

    temp_x = x_souris - POSITION_CARTE_REGION_X;
    temp_y = y_souris - POSITION_CARTE_REGION_Y;
    for(j = 0;  j < 9;  j++)
        for(i = 0;  i < 9;  i++)
        {
            if(temp_x >= i * 4 * UNITE_CARTE_REGION &&
               temp_x <= (i * 4 + 3) * UNITE_CARTE_REGION &&
               temp_y >= j * 4 * UNITE_CARTE_REGION &&
               temp_y <= (j * 4 + 3) * UNITE_CARTE_REGION)
            {
                *x_zone = i;
                *y_zone = j;
                return;
            }
        }
    *x_zone = -1;
    *y_zone = -1;
}



/**
 * \fn static char voir_si_souris_sur_region(const struct resume_souris_echap* saisie,
                                             const struct tab_regions_accessibles* accessibles,
                                             const struct base_donnees* donnees)
 * \brief Permet de savoir si le curseur est positionne
 *        sur une region accessible ou non
 *
 * \param[in] saisie L'adresse de l'etat de la saisie
 * \param[in] accessibles L'adresse de la liste des regions accessibles
 * \param[in] donnees L'adresse d'une base de donnees correctement remplie
 * \return 0 si le curseur n'est sur aucune region accessible,\n
 *         l'id de la region pointee par le curseur
 *         si celui-ci est sur une region accessible
 */
static char voir_si_souris_sur_region(const struct resume_souris_echap* saisie,
                                      const struct tab_regions_accessibles* accessibles,
                                      const struct base_donnees* donnees)
{
    int i;
    unsigned int x_region, y_region, id_region;
    unsigned int x_souris, y_souris;

    for(i = 0;  i < accessibles->nb_elements;  i++)
    {
        id_region = accessibles->tab[i];
        get_coord_region(donnees, id_region, &x_region, &y_region);
        x_region += POSITION_CARTE_MONDE_X;
        y_region += POSITION_CARTE_MONDE_Y;
        GET_COORD_SOURIS(saisie, x_souris, y_souris);
        if(x_region <= x_souris &&
           y_region <= y_souris &&
           x_region + LARGEUR_REGIONS >= x_souris &&
           y_region + HAUTEUR_REGIONS >= y_souris)
            return id_region;
    }

    return 0;
}



/**
 * \fn static void attendre_entree_bataille(const char* texte,
                                            const struct etat_bataille* bataille,
                                            const struct etat_partie* partie,
                                            const struct carte_zone* carte,
                                            const struct base_donnees* donnees)
 * \brief Affiche un message et attend que le joueur appuie sur Entree
 *
 * \param[in] texte Le message a afficher
 * \param[in] bataille L'adresse de l'etat de la bataille
 * \param[in] partie L'adresse de l'etat de la partie
 * \param[in] carte L'adresse de la carte de la zone
 * \param[in] donnees L'adresse d'une base de donnees correctement remplie
 *
 * Notez que cette fonction affiche l'etat de la bataille, ce qui peut
 * engendrer quelques mouvements a l'ecran (ennemis qui bougent sur place,
 * joueur qui se tourne, etc).
 */
static void attendre_entree_bataille(const char* texte,
                                     const struct etat_bataille* bataille,
                                     const struct etat_partie* partie,
                                     const struct carte_zone* carte,
                                     const struct base_donnees* donnees)
{
    unsigned int temps;
    struct resume_saisie saisie;
    rectangle_pour_surface rectangle_a_remplir;

    rectangle_a_remplir.x = 120;
    rectangle_a_remplir.y = 281;
    rectangle_a_remplir.w = 327;
    rectangle_a_remplir.h = 27;
    init_resume_saisie(&saisie);
    do
    {
        temps = obtenir_temps();
        recolter_evenements(&saisie);
        afficher_bataille(bataille, partie, carte, &saisie, temps, donnees);
        remplir_rectangle_surface(donnees->ecran, &rectangle_a_remplir, 0xdddddd);
        ecrire_sur_surface(donnees->ecran, 125, 286,
                           texte,
                           donnees->font_utilisee,
                           30, 30, 30);
        ecrire_sur_surface(donnees->ecran, 124, 285,
                           texte,
                           donnees->font_utilisee,
                           255, 70, 60);
        rafraichir_ecran(donnees->ecran);
    } while(!saisie.entree);
}



/**
 * \fn static char jouer_bataille(struct etat_partie* partie,
                                  const unsigned int num_region,
                                  const unsigned int num_zone,
                                  const struct base_donnees* donnees)
 * \brief Fait jouer une bataille au joueur dans
 *        la zone donnee de la region donnee
 *
 * \param[in,out] partie Un pointeur vers l'etat de la partie du joueur
 * \param[in] num_region L'id de la region dans laquelle se trouve la zone
 *                       dans laquelle se deroulera la bataille
 * \param[in] num_zone L'id de la zone dans laquelle se deroulera la bataille
 * \param[in] donnees L'adresse d'une base_donnee correctement remplie
 * \return VICTOIRE si la bataille est remportee,
 *         DEFAITE si elle a ete perdue ou abandonnee.
 *
 * \warning En cas de victoire, l'etat_partie est mis a jour, mais, en ce qui
 *          concerne l'etat_exploration, il faut consulter la valeur renvoyee
 *          par jouer_bataille et agir en consequence.
 */
static char jouer_bataille(struct etat_partie* partie,
                           const unsigned int num_region,
                           const unsigned int num_zone,
                           const struct base_donnees* donnees)
{
    struct carte_zone carte;
    struct etat_bataille bataille;
    struct resume_saisie saisie;
    char bilan_bataille;
    unsigned int temps;
    struct tableaux_deplacement tabs_deplacement;

    /*
     * Rappel : charger_carte_zone se charge egalement
     * d'initialiser et de remplir l'etat_bataille
     */
    charger_carte_zone(&carte, &bataille, num_region, num_zone, donnees, partie);
    init_tabs_deplacement(&tabs_deplacement, &bataille, &carte);
    init_resume_saisie(&saisie);
    bilan_bataille = PAS_TERMINEE;

    attendre_entree_bataille("Appuyez sur Entree pour commencer",
                             &bataille, partie, &carte, donnees);

    /* ===== boucle d'evenements ===== */
    do
    {
        /* collecte de donnees */
        temps = obtenir_temps();
        recolter_evenements(&saisie);

        /* evenements */
        deplacer_joueur(&bataille, &carte, &saisie, donnees);
        if(saisie.utilisation_objet)
            utiliser_objet(&bataille, partie, &saisie, donnees);
        deplacer_ennemis(&tabs_deplacement, &bataille, &carte, donnees);
        deplacer_projectiles(&carte, &bataille, donnees);
        faire_tirer_joueur(&carte, &bataille, partie, &saisie, donnees);
        faire_tirer_ennemis(&carte, &bataille, donnees);

        /* affichage */
        afficher_bataille(&bataille, partie, &carte, &saisie, temps, donnees);
        rafraichir_ecran(donnees->ecran);

        bilan_bataille = voir_bilan_bataille(&bataille);

#if defined CHEAT
        if(saisie.entree && saisie.utilisation_objet)
            bilan_bataille = VICTOIRE;
#endif /* CHEAT */

        /*
         * il faut empecher la pause si le joueur a perdu, sinon,
         * il pourrait rester en vie indefiniment en mettant
         * pause a chaque tour de boucle...
         */
        if(saisie.quitte && bilan_bataille != DEFAITE)
        {
            demarrer_texte();
            bilan_bataille = menu_bataille();
            arreter_texte();
            if(bilan_bataille != DEFAITE)
                attendre_entree_bataille("Appuyez sur Entree pour reprendre",
                                         &bataille, partie, &carte, donnees);
        }
        attendre(temps);
    } while(bilan_bataille == PAS_TERMINEE);

    /* bataille terminee */
    if(bilan_bataille == VICTOIRE)
        calculer_gains_bataille(&carte, partie, donnees);

    detruire_carte_zone(&carte);
    detruire_etat_bataille(&bataille);
    detruire_tabs_deplacement(&tabs_deplacement);

    return bilan_bataille;
}



void jouer(struct base_donnees* donnees)
{
    struct etat_partie* partie;
    struct etat_exploration exploration;
    char quitter;
    char bilan_bataille;

    partie = creer_partie();
    demarrer_texte();
    if(menu_principal(partie, donnees) == QUITTER)
    {
        /*
         * la partie n'a pas ete remplie, il faut
         * donc la detruire differemment
         */
        free(partie);
        quitter = VRAI;
    }
    else
        quitter = FAUX;
    arreter_texte();
    if(!quitter)
    {
        do
        {
            choisir_region(partie, donnees);
            /*
             * si le numero de region est a 0 apres le choix d'une region,
             * c'est que le joueur souhaite quitter le jeu
             */
            if(get_region_partie(partie) == 0)
                quitter = VRAI;
            if(!quitter)
            {
                /* === debut exploration === */
                init_etat_exploration(&exploration, get_region_partie(partie), donnees);
                do
                {
                    choisir_zone(partie, &exploration, donnees);
                    partie->zone = get_zone_partie(partie);
                    /*
                     * si le numero de zone est a 0 apres le choix d'une zone,
                     * c'est que le joueur a abandonne l'exploration
                     */
                    if(get_zone_partie(partie) != 0)
                    {
                        /* === debut bataille === */
                        bilan_bataille = jouer_bataille(partie, partie->region, partie->zone, donnees);
                        /* === fin bataille === */
                        if(bilan_bataille == VICTOIRE)
                        {
                            marquer_zone_exploree(&exploration, partie->zone);
                        }
                        else
                        {
                            /*
                             * en cas de defaite, le joueur est
                             * renvoye a la carte du monde
                             */
                            partie->zone = 0;
                        }
                    }
                } while(get_zone_partie(partie) != 0);
                /* === fin exploration === */
                detruire_etat_exploration(&exploration);
            }
        } while(!quitter);

        detruire_partie(partie);
    }
}



void choisir_region(struct etat_partie* partie, struct base_donnees* donnees)
{
    /*
     * choix : 0 si aucun choix n'a ete fait, -1 si le joueur souhaite
     * quitter le jeu, un numero de region s'il a choisit d'entrer
     * dans la region qui porte ce numero
     */
    int choix;
    struct tab_regions_accessibles accessibles;
    struct resume_souris_echap saisie;

    init_tab_regions_accessibles(&accessibles, partie, donnees);
    init_resume_souris_echap(&saisie);
    choix = 0;
    do
    {
        afficher_carte_monde(&accessibles, donnees);
        recolter_souris_echap(&saisie);
        if(saisie.clique)
            choix = voir_si_souris_sur_region(&saisie, &accessibles, donnees);
        if(saisie.echap)
        {
            demarrer_texte();
            choix = menu_carte_monde(partie, donnees);
            arreter_texte();
        }
    } while(choix == 0);

    if(choix == -1)
        partie->region = 0;
    else
        partie->region = choix;
}



void choisir_zone(struct etat_partie* partie,
                  const struct etat_exploration* exploration,
                  struct base_donnees* donnees)
{
    struct resume_souris_echap saisie;
    /*
     * 0 si aucun choix, -1 si abandon exploration,
     * 1 si une zone a ete choisie
     */
    char choix_du_joueur;
    char coord_x_zone, coord_y_zone;

    init_resume_souris_echap(&saisie);
    choix_du_joueur = 0;
    do
    {
        recolter_souris_echap(&saisie);
        afficher_carte_region(partie, exploration, donnees);
        if(saisie.clique)
        {
            /* il faut voir sur quelle zone le joueur a clique */
            convertir_pix_en_zone(saisie.x_souris, saisie.y_souris,
                                  &coord_x_zone, &coord_y_zone);
            if(coord_x_zone >= 0 && coord_y_zone >= 0)
            {
                /*
                 * le joueur a cliquer sur une zone, mais il faut encore
                 * verifier que l'emplacement n'est pas vide et que
                 * la zone est accessible
                 */
                if(get_zone_exploration(exploration, coord_x_zone, coord_y_zone) &&
                   voir_si_zone_accessible(exploration, coord_x_zone, coord_y_zone))
                    choix_du_joueur = 1;
            }
        }
        if(saisie.echap)
        {
            demarrer_texte();
            choix_du_joueur = menu_exploration(partie, donnees);
            arreter_texte();
        }
    } while(!choix_du_joueur);
    if(choix_du_joueur == 1)
        partie->zone = get_zone_exploration(exploration, coord_x_zone, coord_y_zone)->id_zone;
    else
        partie->zone = 0;
}



char voir_bilan_bataille(const struct etat_bataille* bataille)
{
    /* on commence par regarder s'il ne reste plus d'ennemis */
    if(est_vide_liste_ennemis(&bataille->ennemis))
        return VICTOIRE;
    /* puis on regarde si le joueur est mort */
    if(!get_pv_actuels_joueur(bataille))
        return DEFAITE;
    /* si aucun des deux cas n'est vrai, la bataille n'est pas terminee */
    return PAS_TERMINEE;
}


